Miles Sound System SDK 7.2a

Accessing the Codecs Directly with the RIB Interface

Discussion

Most MSS applications don't need to access the library's ASI (Audio Stream Interface) layer directly - the details of communicating with individual ASI providers are encapsulated by high-level library functions such as the streaming and quick-integration APIs. However, real-time voice chat is an exception. To achieve good latency performance and full control over the compression/decompression process, it's necessary for the application to gain low-level access to the Voxware codecs by means of the ASI interface.

The ASI standard represents a very simple, generalized interface for MSS-compatible audio compression and decompression codecs. ASI uses the RAD Interface Broker (RIB) architecture to allow various codecs to be enumerated and selected on the basis of any of their properties and capabilities, including the standard file suffix of the data format they support. (See the RIB Architecture chapter for more information on the RIB interface model.) MSSCHTC.CPP and MSSCHTS.CPP demonstrate the process of selecting an ASI codec to handle a given input or output data format. The ASI codecs responsible for delivering the Voxware MetaVoice functionality are supplied in the MSSVoice.ASI file and include: include:

A 2900-bps high-quality fixed-rate compressor/decompressor. Suffix: ".V29"

A 2400-bps fixed-rate compressor/decompressor. Suffix: ".V24"

A 1200-bps low-bandwidth variable-rate compressor/decompressor. Suffix: ".V12"

By using each ASI codec's file suffix as a descriptor of the data format in use, MSSCHTC and MSSCHTC illustrate a way to make your chat system independent of any particular data format. When you run the Miles chat front-end (MSSCHAT.EXE), it calls MSSCHTC.EXE with the codec to use specified on the Windows command line. The client will try to find an ASI codec capable of both compression and decompression of data with the specified suffix type. Likewise, it will inform the server during the logon process that all data coming from the client will be compressed in the specified format, and that all data sent back to the client should be delivered in the same format. If both the client and the server are able to locate an ASI provider for the specified format, the link can be successfully established.

As an example of direct ASI codec access, MSSCHTC uses RIB services to locate an ASI encoder (compressor) for the data format specified on its command line.


//
// Find ASI provider to encode voice data for transmission
//
transmit_encoder = RIB_find_file_provider("ASI codec",
"Output file types",
suffix);
if (transmit_encoder == NULL)
{
show_error("Error: No ASI provider available to encode data type %s\n", suffix);
exit(1);
}
show_success("Using ASI provider %X to transmit data type %s\n",
transmit_encoder,
suffix);

Once the transmit encoder provider is obtained, MSSCHTC calls RIB to obtain an interface to various functions supported by the ASI codec. (As with all RIB components, it is the requestor's responsibility to specify the interface it wants to use when communicating with a RIB service provider).


//
// Get ASI stream interface for transmit encoder
//
RIB_INTERFACE_ENTRY ASISTR[] =
{
{ RIB_FUNCTION, "ASI_stream_open", (U32) &XMIT_stream_open, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_close", (U32) &XMIT_stream_close, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_process", (U32) &XMIT_stream_process, RIB_NONE }
}
RIB_request(transmit_encoder,"ASI stream",ASISTR);
//
// Open stream with codec, registering callback function
//
transmit_stream = XMIT_stream_open(0,
transmit_stream_callback,
0);
if (transmit_stream == NULL)
{
show_error("Could not open stream encoder\n");
exit(1);
}

Subsequently, a similar block of code is used to locate an ASI provider capable of decompressing the same data format as it is received from the server. The only difference in this case is the use of the "Input file types" property as the search key passed to RIB_find_file_provider.


//
// Search for ASI codec capable of processing specified input file type
//
// This ASI provider will be used to decode data sent from the server
// to the client
//
receive_decoder = RIB_find_file_provider("ASI codec",
"Input file types",
suffix);
if (receive_decoder == NULL)
{
show_error("Error: No ASI provider available to decode data type %s\n",
suffix);
exit(1);
}

To illustrate a "well-behaved" application's interaction with an ASI provider, the client receive provider's interface contains requests for a few key properties, in addition to the same basic functions as those previously obtained from the transmit provider:


//
// Get ASI stream interface for receive decoder
//
RIB_INTERFACE_ENTRY RECVSTR[] =
{
{ RIB_FUNCTION, "ASI_stream_property", (U32) &RECV.ASI_stream_property, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_open", (U32) &RECV.ASI_stream_open, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_seek", (U32) &RECV.ASI_stream_seek, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_close", (U32) &RECV.ASI_stream_close, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_process", (U32) &RECV.ASI_stream_process, RIB_NONE },
{ RIB_PROPERTY, "Output sample rate", (U32) &RECV.OUTPUT_SAMPLE_RATE, RIB_NONE },
{ RIB_PROPERTY, "Output sample width", (U32) &RECV.OUTPUT_BITS, RIB_NONE },
{ RIB_PROPERTY, "Output channels", (U32) &RECV.OUTPUT_CHANNELS, RIB_NONE },
{ RIB_PROPERTY, "Requested sample rate",(U32) &RECV.REQUESTED_RATE, RIB_NONE },
};
RIB_request(receive_decoder,"ASI stream",RECVSTR);
//
// Open the receiver stream
//
// If the codec needs to inspect the stream data to
// configure itself, this call will block in the RECV_stream_CB()
// handler until the requested amount of data (typically only
// the first few bytes of the source stream) is received from the
// client.
//
RECV_read_cursor = 0;
RECV_write_cursor = 0;
RECV.stream = RECV.ASI_stream_open(0,
RECV_stream_CB,
0);
if (RECV.stream == NULL)
{
show_error("Could not open stream decoder\n");
exit(1);
}

These properties are used to request operation at the standard 8 kHz sample rate (HW_RATE) and to obtain the actual data format used by the codec to configure the output stream's HSAMPLE:


//
// Request codec output rate which matches hardware rate
//
U32 req_rate = HW_RATE;
RECV.ASI_stream_property(RECV.stream,
RECV.REQUESTED_RATE,
0, &req_rate, 0 );
//
// Stream is now open -- get its properties and set output
// sample properties accordingly
//
U32 nch, rate, bits;
RECV.ASI_stream_property(RECV.stream, RECV.OUTPUT_CHANNELS, &nch, 0, 0 );
RECV.ASI_stream_property(RECV.stream, RECV.OUTPUT_SAMPLE_RATE, &rate, 0, 0 );
RECV.ASI_stream_property(RECV.stream, RECV.OUTPUT_BITS, &bits, 0, 0 );
S32 type;
if (nch == 2)
{
type = ( (bits == 16) ? DIG_F_STEREO_16 : DIG_F_STEREO_8 ) |
( (bits == 16) ? DIG_PCM_SIGN : 0 );
}
else
{
type = ( (bits == 16) ? DIG_F_MONO_16 : DIG_F_MONO_8 | )
( (bits == 16) ? DIG_PCM_SIGN : 0 );
}
AIL_init_sample (stream, type, 0);
AIL_set_sample_playback_rate(stream, rate);
receive_buffer_size = AIL_minimum_sample_buffer_size(dig, rate, type);
show_info("Receive stream format: %d channels, %d Hz, %d bits\n",nch,rate,bits);
show_info("Receive buffer size=%d\n",receive_buffer_size);

In reality, as noted previously, all MSS ASI providers used for voice communication are internally hardwired for operation with 16-bit monaural PCM audio at a sample rate of 8 kHz, so the above code to query the stream properties is not, strictly speaking, necessary. However, besides being good programming (or at least typing) practice, it serves as an example of how to obtain properties and set properties for an ASI stream. This general technique will be useful if the application wishes to take advantage of the Voxware warping and comfort-noise masking features described below.

Once the transmit and receive providers have been selected and their corresponding streams opened successfully, all that remains to be done on the client side with respect to ASI is the actual encoding and decoding of data. The following except from receive_ASI_thread_procedure in MSSCHTC.CPP illustrates the use of the ASI_stream_process function to turn compressed audio data back into playable PCM format:


//
// If fetch buffer empty, call ASI decoder to acquire decompressed data
// from server
//
// This will block in RECV_stream_CB() until enough data is received
// from the server to fill the fetch buffer
//
// Using the temporary fetch buffer allows us to read and decompress
// data while all of the receive buffers are full -- otherwise, we'd
// have to spin until an empty receive buffer becomes available
//
if (!fetch_buffer_full)
{
S32 amount = RECV.ASI_stream_process(RECV.stream,
fetch_buffer,
receive_buffer_size);
if (amount != receive_buffer_size)
{
//
// Bad read
//
active = 0;
return 0;
}
fetch_buffer_full = 1;
}

Next Topic (Performing the Compression and Decompression)

Previous Topic (Working with Voice Input)


Group: Implementing Voice Chat

For technical support, e-mail Miles3@radgametools.com
© Copyright 1991-2007 RAD Game Tools, Inc. All Rights Reserved.